Codes
Code your ideas for understanding of natural systems
Updated at 2021.1.3
Updated at 2020.11.01
Updated at 2020.09.28
Intro
Markdown 편집기를 Blazor 프레임워크를 활용하여 웹페이지로 만들어 보자. VSCode만한 편집기가 없지만, 웹에서 직접 간단히 편집을 할 수 있고 나의 용도에 맞게 커스터마이즈할 수 있기 때문에 시도해 볼 만하다. 구글링을 통해 여러 가지 정보를 참고했지만, 다음 사이트를 가장 많이 참조하였다.
Creating an ASP.NET Core Markdown TagHelper and Parser
Install Nuget Package
Markdig 0.2.1
: 기본 기능만 사용할 경우Westwind.AspNetCore.Markdown 3.4.0
: Pipeline 등 고급 기능을 사용할 경우
Making Simple Editor at Blazor WebAssembly
만들고자 하는 것은 3개의 칼럼으로 나누어져 있다.
- 첫번째는 마크다운 텍스트 입력,
- 두번째는 마크업(HTML)으로 변환된 문서를,
- 세번째는 그 HTML 문서를 Viewer로 보여준다.
Start new Blazor WebAssembly project
문법에 대한 상세한 설명을 제외하고 간단하게 핵심 스토리만 이야기하면,
textarea
에 텍스트가 변했을 때OnTextChanged
함수를 호출하고,- 입력된 문자열을
MarkdownPipelineBuilder
를 이용하여 HTML로 변환한다. - 변환된 문자열을
MarkupString
으로 타입을 변환해 HTML Viewer로 보여준다.
Making MDEditor razor file
MDEditor.razor: 세 개의 칼럼으로 나누고 textarea
입력 발생 시 OnTextChanged
이벤트 처리 함수를 연결 시키고, 변환된 문자열과 그것을 MarkupString
타입 변환 것을 보여주게 한다.
@page "/MDEditor"
<div class="row">
<div class="col-4">
<textarea rows="20" class="form-control" @oninput="OnTextChanged"></textarea>
</div>
<div class="col-4">
@Preview
</div>
<div class="col-4">
@((MarkupString)Preview)
</div>
</div>
Making MDEditor razor cs file
MDEditor.razor.cs: Razor파일 내에 cs
코드를 넣을 수도 있지만, 여기서는 개별 파일을 만들어서 OnTextChanged
이벤트 처리 함수를 구현한다. ChangedEventArgs
를 파라미터로 받아들여 파싱을 하고, 자동으로 화면을 업데이트하기 위해 StateHasChanged
함수를 비동기로 호출한다.
using Microsoft.AspNetCore.Components;
using Markdig;
namespace ArchivesdjWork1.Pages
{
public partial class MDEditor : ComponentBase
{
public string Preview { get; set; }
protected void OnTextChanged(ChangeEventArgs args)
{
Preview = Parse(args.Value.ToString());
InvokeAsync(() => StateHasChanged());
}
public static string Parse(string markdown)
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
return Markdown.ToHtml(markdown, pipeline);
}
}
}
Making Simple Editor at Blazor Server
Blazor Server 프레임워크에 동일한 기능을 구현해 보자. 텍스트가 변경될 때마다 변환 함수를 호출하는 것은 속도도 늦고 비효율적이기 때문에 Westwind.AspNetCore.Markdown
Nuget Package를 설치하여 업그레이드 해보자.
Making MarkdownParser Interface
IMarkdownParser: Parse
메서드를 멤버로 가지는 인터페이스를 만든다.
namespace ArchivesdjWork2.Pages.Markdown
{
public interface IMarkdownParser
{
public string Parse(string markdown, bool sanitizeHtml = true);
}
}
Making Wrapper around Markdig with Cached Instance
MarkdownParserMarkdig.cs: 상세한 함수 및 활용법은 Westwind.AspNetCore.Markdown
를 찾아 봐야한다. 아직 다 파악하지 못했다. 기본 뼈대만 정리하면,
- Constructor를 1개 만들어서
MarkdownPipeline
을 생성한다. IMarkdownParser
인터페이스를 상속 받았으므로,Parse
함수를 구현한다. 상세 내용은Markdig
라이브러리를 찾아 볼 필요가 있다.MardownParserBase
에서 상속 받은 2개의 함수를 구현한다.- Constructor에서 호출한
CreatePipelineBuilder
를 구현한다. 생성시 다양한 옵션 처리를 하느라 복잡하게 긴데 내용은 심플하다. CreateRenderer
은 단순히HTMLRenderer
를 생성하여 호출한다.
- Constructor에서 호출한
using Markdig;
using Markdig.Extensions.AutoIdentifiers;
using Markdig.Renderers;
using System;
using System.IO;
using Westwind.AspNetCore.Markdown;
namespace ArchivesdjWork2.Pages.Markdown
{
/// <summary>
/// Wrapper around the MarkDig parser that provides a cached
/// instance of the Markdown parser. Hooks up custom processing.
/// </summary>
public class MarkdownParserMarkdig : MarkdownParserBase, IMarkdownParser
{
public static MarkdownPipeline Pipeline;
private readonly bool _usePragmaLines;
public MarkdownParserMarkdig(bool usePragmaLines = false, bool force = false, Action<MarkdownPipelineBuilder> markdigConfiguration = null)
{
_usePragmaLines = usePragmaLines;
if (force || Pipeline == null)
{
var builder = CreatePipelineBuilder(markdigConfiguration);
Pipeline = builder.Build();
}
}
/// <summary>
/// Parses the actual markdown down to html
/// </summary>
/// <param name="markdown"></param>
/// <returns></returns>
public override string Parse(string markdown, bool sanitizeHtml = true)
{
if (string.IsNullOrEmpty(markdown))
return string.Empty;
var htmlWriter = new StringWriter();
var renderer = CreateRenderer(htmlWriter);
Markdig.Markdown.Convert(markdown, renderer, Pipeline);
var html = htmlWriter.ToString();
html = ParseFontAwesomeIcons(html);
return html;
}
public virtual MarkdownPipelineBuilder CreatePipelineBuilder(Action<MarkdownPipelineBuilder> markdigConfiguration)
{
MarkdownPipelineBuilder builder = null;
// build it explicitly
if (markdigConfiguration == null)
{
builder = new MarkdownPipelineBuilder()
.UseEmphasisExtras()
.UsePipeTables()
.UseGridTables()
.UseFooters()
.UseFootnotes()
.UseCitations()
.UseAutoLinks() // URLs are parsed into anchors
.UseAutoIdentifiers(AutoIdentifierOptions.GitHub) // Headers get id="name"
.UseAbbreviations()
.UseYamlFrontMatter()
.UseEmojiAndSmiley(true)
.UseMediaLinks()
.UseListExtras()
.UseFigures()
.UseTaskLists()
.UseCustomContainers()
.UseMathematics()
.UseGenericAttributes();
if (_usePragmaLines)
builder = builder.UsePragmaLines();
return builder;
}
// let the passed in action configure the builder
builder = new MarkdownPipelineBuilder();
markdigConfiguration.Invoke(builder);
if (_usePragmaLines)
builder = builder.UsePragmaLines();
return builder;
}
protected virtual IMarkdownRenderer CreateRenderer(TextWriter writer)
{
return new HtmlRenderer(writer);
}
}
}
Retrieving Instance of a Markdown Parser
MarkdownParserFactory: IMarkdownParser
를 정적으로 1개 생성하여 GetParser
호출 시 반환하는 정적 클래스이다.
namespace ArchivesdjWork2.Pages.Markdown
{
/// <summary>
/// Retrieves an instance of a markdown parser
/// </summary>
public static class MarkdownParserFactory
{
/// <summary>
/// Use a cached instance of the Markdown Parser to keep alive
/// </summary>
static IMarkdownParser CurrentParser;
/// <summary>
/// Retrieves a cached instance of the markdown parser
/// </summary>
/// <param name="forceLoad">Forces the parser to be reloaded - otherwise previously loaded instance is used</param>
/// <param name="usePragmaLines">If true adds pragma line ids into the document that the editor can sync to</param>
/// <returns>Mardown Parser Interface</returns>
public static IMarkdownParser GetParser(bool usePragmaLines = false,
bool forceLoad = false)
{
if (!forceLoad && CurrentParser != null)
return CurrentParser;
CurrentParser = new MarkdownParserMarkdig(usePragmaLines, forceLoad);
return CurrentParser;
}
}
}
Adding Service
Startup.cs: Markdig
라이브러리에서 구현된 AddMarkdown
메서드를 호출하여 Service
에 등록한다.
public void ConfigureServices(IServiceCollection services)
{
// 기존 Services
...
// 신규 Service
services.AddMarkdown(config =>
{
// Create custom MarkdigPipeline
// using MarkDig; for extension methods
config.ConfigureMarkdigPipeline = builder =>
{
builder.UseEmphasisExtras(Markdig.Extensions.EmphasisExtras.EmphasisExtraOptions.Default)
.UsePipeTables()
.UseGridTables()
.UseAutoIdentifiers(AutoIdentifierOptions.GitHub) // Headers get id="name"
.UseAutoLinks() // URLs are parsed into anchors
.UseAbbreviations()
.UseYamlFrontMatter()
.UseEmojiAndSmiley(true)
.UseListExtras()
.UseFigures()
.UseTaskLists()
.UseCustomContainers()
.UseMathematics()
.UseGenericAttributes();
};
});
}
Modifying MDEditor razor cs file
MDEditor.razor.cs: MdEditor.razor
는 위의 WebAssembly
내용과 동일하고 cs
파일만 하기와 같이 수정하면 된다. 정적 클래스로 만들어 둔 MarkdownParserFactory
를 활용하는 것만 차이가 있다.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Html;
using ArchivesdjWork2.Pages.Markdown;
namespace ArchivesdjWork2.Pages
{
public partial class MDEditor : ComponentBase
{
public string Preview { get; set; }
protected void OnTextChanged(ChangeEventArgs args)
{
Preview = Parse(args.Value.ToString());
InvokeAsync(() => StateHasChanged());
}
/// <summary>
/// Renders raw markdown from string to HTML
/// </summary>
/// <param name="markdown"></param>
/// <param name="usePragmaLines"></param>
/// <param name="forceReload"></param>
/// <returns></returns>
public static string Parse(string markdown, bool usePragmaLines = false, bool forceReload = false)
{
if (string.IsNullOrEmpty(markdown))
return "";
var parser = MarkdownParserFactory.GetParser(usePragmaLines, forceReload);
return parser.Parse(markdown);
}
/// <summary>
/// Renders raw Markdown from string to HTML.
/// </summary>
/// <param name="markdown"></param>
/// <param name="usePragmaLines"></param>
/// <param name="forceReload"></param>
/// <returns></returns>
public static HtmlString ParseHtmlString(string markdown, bool usePragmaLines = false, bool forceReload = false)
{
return new HtmlString(Parse(markdown, usePragmaLines, forceReload));
}
}
}
Summary
효율성을 위해 동적으로 Markdown 문서를 편집할 때는 두번째 방법을 사용하면 되지만,
Blazor Server
에서도 정적으로 md 문서를 열어서 보여 주기만 할때는 첫번째 방법이 이해하기도 쉽고 간단하다. Simple is the Best.
총 17 개의 글이 있습니다.
# | 제목 | 날짜 | 조회수 |
---|---|---|---|
01 | CS 배우기 요약 | 2021/06/07 | 145 |
02 | CS Statements | 2021/06/07 | 128 |
03 | 퍼셉트론 | 2021/04/15 | 125 |
04 | Blazor and Sqlite | 2021/04/15 | 137 |
05 | Blazor Layouts | 2021/04/15 | 161 |
06 | CS Language Reference | 2021/06/07 | 127 |
07 | VSCode and Markdown | 2021/04/15 | 138 |
08 | Blazor에서 이미지파일 다루기 | 2021/06/10 | 211 |
09 | Blazor and Markdown | 2021/04/15 | 145 |
10 | 종속성 주입 | 2021/06/07 | 152 |
11 | Blazor에서 데이터 다루기 | 2021/06/07 | 137 |
12 | Blazor Components | 2021/04/15 | 148 |
13 | CS Glossary | 2021/06/07 | 126 |
14 | Enum 타입 다루기 | 2021/12/14 | 135 |
15 | 생활코딩 CS01 | 2022/04/25 | 261 |
16 | 생활코딩 CS02 | 2022/04/30 | 165 |
17 | 생활코딩 CS03 | 2022/04/30 | 441 |